<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JavaScript面向对象编程（二）：构造函数的继承</title>

<!-- JS代码  -->
<script type="text/javaScript">

/**
 *  这个系列的第一部分，主要介绍了如何"封装"数据和方法，以及如何从原型对象生成实例。
 *  今天要介绍的是，对象之间的"继承"的五种方法。
 */
 
 //比如，现在有一个"动物"对象的构造函数。
 function Animal(){
	this.type = "父类";
	this.species = "动物";
	this.description = function(){
		console.log("我是父类的描述方法。。。。。。");
	};
}

 //还有一个"猫"对象的构造函数。
 function Cat(name,color){
	 this.name = name;
	 this.color = color;
 }

/**怎样才能使"猫"继承"动物"呢？**/
 
 /*
 *  一、 构造函数绑定 call或apply方法
 *  第一种方法也是最简单的方法，使用call或apply方法。
 *  将父对象的构造函数绑定在子对象上，即在子对象构造函数中加一行call或apply方法，格式如下：
 */
 //声明一个新的Cat"猫"对象，叫Cat1继承Animal
 function Cat1(name,color){
	Animal.apply(this,arguments);
	//Animal.call(this,"");
	this.name = name;
	this.color = color;
}

/**
 *  通过上面的代码，Cat1就继承了Animal中的所有属性和方法。
 *  调用看一下结果：
 */
var cat1 = new Cat1("大毛","白色");
console.log("cat1.type："+cat1.type);
console.log("cat1.species："+cat1.species);
cat1.description();
console.info("输出一下cat1这个对象：");
console.dir(cat1);

/****
 *  二、 通过prototype模式
 *
 *  第二种方法更常见，使用prototype属性。
 *  如果"猫"的prototype对象，指向一个Animal的实例，那么所有"猫"的实例，就能继承Animal了。
 */
 
 //代码：现在用Cat类  来做实例讲解
 Cat.prototype = new Animal();//1.要理解这句是什么意思：我们将Cat的prototype对象指向一个Animal的实例。
 
 console.info("查看Cat原型链中的构造器是否指向了Animal-->Cat.prototype.constructor == Animal：");
 console.log(Cat.prototype.constructor == Animal);
 console.info("查看此时Cat原型链中的构造器指向了谁：");
 console.log(Cat.prototype.constructor);
 
 Cat.prototype.constructor = Cat;//2.也要理解这句话是什么意思，重点理解。
 
 console.info("更改之后查看此时Cat原型链中的构造器指向了谁：");
 console.log(Cat.prototype.constructor);
 var catObj = new Cat("猫","棕色");
 console.log("catObj.type："+catObj.type+"，catObj.species："+catObj.species);
 catObj.description();
 console.info("输出一下catObj这个对象：");
 console.dir(catObj);
 
 /**
 *  解析：
 *  1.代码的第一行，我们将Cat的prototype对象指向一个Animal的实例。
 *  Cat.prototype = new Animal();
 *  它相当于完全删除了Cat.prototype 对象原先的值，然后重新赋予一个新值。
 *  2.但是，第二行又是什么意思呢？
 *  Cat.prototype.constructor = Cat;
 *  原来，任何一个类的prototype对象都有一个constructor属性，指向它的构造函数。
 *  一开始Cat还没有赋值(new Animal()创建实例)的时候，Cat.prototype.constructor是指向Cat的；
 *  而加了"Cat.prototype = new Animal();"（重新赋值） 这一行以后，Cat.prototype.constructor会指向Animal。
 *  alert(Cat.prototype.constructor == Animal); //true
 *  所以第二行要重新赋值他的constructor构造方法为Cat-->Cat.prototype.constructor = Cat;
 *
 *  更重要的是，每一个实例也有一个constructor属性，默认调用prototype对象的constructor属性。
 *  就是每一个实例的构造器，默认初始化时都是调用的原型链对象的构造器.
 *  alert(catObj.constructor == Cat.prototype.constructor); // true
 *
 *  因此，在运行"Cat.prototype = new Animal();"这一行之后，catObj.constructor也指向Animal！
 *  alert(catObj.constructor == Animal); // true
 *  这显然会导致继承链的紊乱（catObj明明是用构造函数Cat生成的）
 *  因此我们必须手动纠正，将Cat.prototype对象的constructor值改为Cat。这就是第二行的意思。
 *  这是很重要的一点，编程时务必要遵守。下文都遵循这一点，即如果替换了prototype对象，
 *  o.prototype = {};
 *  那么，下一步必然是为新的prototype对象加上constructor属性，并将这个属性指回原来的构造函数。
 *  o.prototype.constructor = o;
 **/
 
 
 
/**
 *  三、 直接继承prototype
 *
 *  第三种方法是对第二种方法的改进。
 *  由于Animal对象中，不变的属性都可以直接写入Animal.prototype。
 *  所以，我们也可以让Cat()跳过 Animal()，直接继承Animal.prototype。
 */
 
 /*
 *  现在，我们先将Animal对象改写为Animal2对象来演示实例：
 *  
 */
 function Animal2(){ } 
     Animal2.prototype.type = "父类";
     Animal2.prototype.species = "动物";
     Animal2.prototype.description = function(){
    	  console.log("我是父类Animal2的描述方法。。。。。。");
	 };
 
 function Cat2(name,color){
	 this.name = name;
	 this.color = color;
 }
 
 Cat2.prototype = Animal2.prototype; //这一句的作用是把Animal2的原型链赋值给Cat2的原型链。
 console.info("更改Cat2原型链对象的构造方法之前，查看Animal2的原型链构造法是那个：");
 console.log(Animal2.prototype.constructor);
 //其实真正的意思是：Cat2的原型链指向了Animal2的原型链，俩个对象的原型链prototype共用一个内存地址，改变那个一的值，其它都跟着改！
 Cat2.prototype.constructor = Cat2;
 console.warn("注意了更改Cat2原型链对象的构造方法之后，Animal2的原型链构造法也跟着改变了：");
 console.log(Animal2.prototype.constructor);
 
 
 var cat2 = new Cat2("二毛","黑色");
 console.log("cat2.type："+cat2.type+"，cat2.species："+cat2.species);
 cat2.description();
 console.info("输出一下cat2这个对象：");
 console.dir(cat2);
 
 
 /*
 *
 *  与前一种方法相比，这样做的
 *  ①. 优点是效率比较高（不用执行和建立Animal的实例了），比较省内存。
 *  ②. 缺点是 Cat2.prototype和Animal.prototype现在指向了同一个对象;
 *        那么任何对Cat2.prototype的修改，都会反映到Animal.prototype。
 *
 *  所以，上面这一段代码其实是有问题的。请看下面这行代码：
 *              Cat2.prototype.constructor = Cat2;
 *
 *  这一句实际上把Animal.prototype对象的constructor属性也改掉了！
 *
 *  　　console.log(Animal2.prototype.constructor); // Cat2
 *
 */ 
 
 
 /*
 *  四、利用空对象作为中介
 *  由于"三、直接继承prototype"存在上述的缺点，所以就有第四种方法，利用一个空对象作为中介。
 */
 function Animal3(){ } 
 Animal3.prototype.type = "父类";
 Animal3.prototype.species = "动物";
 Animal3.prototype.description = function(){
     console.log("我是父类Animal3的描述方法。。。。。。");
 };
 
 function Cat3(name,color){
     this.name = name;
     this.color = color;
 }
 
 //主要代码部分
 var F = function(){}; 
 F.prototype  = Animal3.prototype;
 Cat3.prototype = new F();
 Cat3.prototype.constructor = Cat3;
 console.info("Cat3的原型链对象的构造方法重新赋值以后，查看Animal3原型链对象中的构造方法会不会受影响Animal3.prototype.constructor：");
 console.log(Animal3.prototype.constructor);//Animal3
 
 /*
 *    F是空对象，所以几乎不占内存。这时，修改Cat的prototype对象，就不会影响到Animal的prototype对象。
 *　console.log(Animal.prototype.constructor); // Animal
 */
 
 //我们将上面的主要代码部分，封装成函数便于使用。
 function extend(Child,Parent){
	 var F = function(){};
	 F.prototype = Parent.prototype;
	 Child.prototype = new F();
	 Child.prototype.constructor = Child;
	 Child.uber = Parent.prototype;  //意思是为子对象设一个uber属性，这个属性直接指向父对象的prototype属性。
 }
 
 //使用方法如下：
 extend(Cat3,Animal3);
 var cat3 = new Cat3("红毛","红色");
 console.info("通过extend方法继承之后，输出一下cat3对象：");
 console.dir(cat3);
 
 /*
 *  这个extend函数，就是YUI库如何实现继承的方法。
 *  另外，说明一点，函数体最后一行
 *  Child.uber = Parent.prototype;
 *  意思是为子对象设一个uber属性，这个属性直接指向父对象的prototype属性。
 *（uber是一个德语词，意思是"向上"、"上一层"。）这等于在子对象上打开一条通道，可以直接调用父对象的方法。
 * 这一行放在这里，只是为了实现继承的完备性，纯属备用性质。
 */
 
 
 /**
 *  五、 拷贝继承
 * 上面是采用prototype对象，实现继承。我们也可以换一种思路，纯粹采用"拷贝"方法实现继承。
 *  简单说，如果把父对象的所有属性和方法，拷贝进子对象，不也能够实现继承吗？这样我们就有了第五种方法。
 */
 
 //首先，还是把Animal的所有不变属性，都放到它的prototype对象上。
 function Animal4(){ } 
     Animal4.prototype.type = "父类";
     Animal4.prototype.species = "动物";
     Animal4.prototype.description = function(){
    	   console.log("我是父类Animal4的描述方法。。。。。。");
      };
 
 function Cat4(name,color){
     this.name = name;
     this.color = color;
 }
 
/*
 *  然后，再写一个函数，实现属性拷贝的目的。
 *  这个函数的作用，就是将父对象的prototype对象中的属性，一 一 拷贝给Child对象的prototype对象。
 */
 function extend2(Child,Parent){
	 var p = Parent.prototype;
	 var c = Child.prototype;
	 for(var i in p){
		 c[i] = p[i];
	 }
	 c.uber = p;
 }

//使用的时候，这样写：
    extend2(Cat4, Animal4);
    var cat4 = new Cat4("紫毛","紫色");
    console.info("通过extend2方法继承之后，输出一下cat4对象：");
    console.dir(cat4);

</script>
</head>
<body>
<h3>JavaScript面向对象编程（二）：构造函数的继承</h3>
<h1><a href="http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html">参考地址链接.</a></h1>

请打开控制台，查看console对象的输出情况！！！
</body>
</html>